// Basestation version 0_1
// Code to command the Arduino basestation for an outdoor timelapse camera
/* Copyright 2013 Mike Henrey 

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along 
with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

/* This code was developed and tested for use with Arduino 1.05 */

/* pin assignment list
DIGITAL PINS
0, 1: serial port
2, 3, 4, 5, 6, 7: LCD
8, 11, 12, 13: SD card
9, 10, 13: DIP switch shift register (note conflict of pin 13 will need to be resolved)
ANALOG PINS
0: potentiometer
1: Sharp distance sensor
2: PIR sensor
3: DIP switch conflicted pin
4: ? 
5: ?
*/
#include <SdFat.h>          // library for the SD card
#include <LiquidCrystal.h>  // library for the LCD display
#include <LinkSpriteCam.h>  // library for the Camera

// function protocols
void OpenSDFile(int fileCounter);
void WriteBytesToFile(byte *serData, int numBytes);
word CapturePhoto();

// Circuit requirements for the SD card:
// SD card attached to SPI bus as follows:
 // MOSI - pin 11
 // MISO - pin 12
 // CLK - pin 13
 // CS - pin 8
// chipSelect pin for the SD card
const int chipSelect = 8; 

// the sd card objects
SdFat  sd;
SdFile  myFile;
SdFile  root;
SdBaseFile baseFile;

// The LCD Setup
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);    // the LCD module
const int LCD_WIDTH = 16;
const int LCD_ROWS = 2;

// The potentiometer
const int POTENTIOMETER = A0;
int potentiometerValue = 0;

// The timer
unsigned long  startTime = 0;
unsigned long  currentTime = 0;
unsigned long  elapsedTime = 0;


// some constants used in the code
const int READ_BUFF_SIZE = 64;    // Size of the software buffer (HW buffer set to 32)
const int SPACING_INTERVAL = 100;  // time delay between reads, multiple of 10
const int TIMEOUT_MAX = 1000;    // number of reads before a missing data is assumed

// time between photos
unsigned int timeBetweenPhotos = 5;    // extra time between shots in ms, added to the transfer time of ~17 s

// make a new camera object
LinkSpriteCam LCamera;

void setup()
{ 
  LCamera.Begin(38400, READ_BUFF_SIZE, SPACING_INTERVAL);      // begins the serial connection
  pinMode(2, INPUT);          // for the LCD (is this necessary???)
  
  // set up the LCD's number of columns and rows: 
  lcd.begin(LCD_WIDTH, LCD_ROWS);
  
  // Setup the camera
  if (LCamera.SendResetCmd()){
    lcd.print("Camera reset");
  }
  else{
    lcd.print("Reset failed");
  }
  
  lcd.setCursor(0,1);
  // now wait for the init end to arrive
  if (LCamera.ReadInitSequence()){
    lcd.print("Init OK");
  }
  else{
    lcd.print("Init failed");
  }
  delay(3000);
  lcd.setCursor(0,0);
  // set resolution to 640x320
  if (LCamera.SetLargeSizeCmd()){
     lcd.print("Size: 640x320   ");
  }
  else{
    lcd.print("Size change fail");
  }
    
  delay(2000);
  
  // Initialize SdFat or print a detailed error message and halt
  if (!sd.begin(chipSelect, SPI_FULL_SPEED))
    sd.initErrorHalt();
}

void loop() {
  // local flags
  boolean endFlag;
  
  int j=0;
  int i;
  int m;
  int count=0;
  int timeout;
  int fileCounter;
  
  int readStatus;

  byte serData[READ_BUFF_SIZE];
  word photoSize;

  // tell the system to capture the next photo
  for (fileCounter = 0; fileCounter < 2000; ++fileCounter){
    // reset the flags
    endFlag = 0;
    
    // reset the file location
    LCamera.Seta(0x0000);
    
    // open the next file in the list
    OpenSDFile(fileCounter);
    
    // clear the hardware buffer
    delay(100);
    while(Serial.available()){
      Serial.read();
    }
    
    // tell the camera to take a photo
    photoSize = CapturePhoto();
    
    // Setup LCD Display
    lcd.home();                // home
    lcd.print("Photo ");
    lcd.print(fileCounter);
    lcd.setCursor(0,1);      // line 2
    lcd.print("Next photo ");

    // send the command to get some data for first iteration
    LCamera.SendReadDataCmd();
    
    // get the start time
    startTime = millis();
    
    // now transfer the photo over wireless and log to file
    while(!endFlag){
      
      // get the elapsed time
      currentTime = millis();
      elapsedTime = (currentTime - startTime)/1000;

      // figure out how long inbetween photos
      timeBetweenPhotos = GetTimeBetweenPhotos();
      
       // update the LCD screen
      lcd.home();
      lcd.setCursor(12, 0);
      lcd.print(photoSize-(photoSize%READ_BUFF_SIZE)-LCamera.Geta(), HEX);
      lcd.setCursor(11, 1);
      if((timeBetweenPhotos - elapsedTime) > 0){
        lcd.print(timeBetweenPhotos - elapsedTime);
      }
      else{
        lcd.print("XXX");
      }
      lcd.print("   ");    // write some zeros to clear stuff
      
      // fill the read buffer from the serial port
      readStatus = LCamera.FillReadBuffer(serData);
      
      if (readStatus == -1){
        // handle resending the last frame
        LCamera.Decreasea(READ_BUFF_SIZE);
        // don't bother writing to file, just resend
        LCamera.SendReadDataCmd();
      }
      else if (readStatus != READ_BUFF_SIZE){
        // file transfer complete
        WriteBytesToFile(serData, readStatus);
        endFlag = 1;
      }
      else{
        // normal operation. Send the request for more data and begin saving
        LCamera.SendReadDataCmd();
        WriteBytesToFile(serData, READ_BUFF_SIZE);
      }
    }
        
      
    // clear anything left in the buffer
    delay(10);
    while(Serial.available() > 0){
      Serial.read();
    }
    LCamera.StopTakePhotoCmd();
    delay(100);
    
    // go into power savings
    LCamera.StartPowerSavingsCmd();
   
    // close the file
    lcd.home();    // home
    lcd.print("Transfer DONE   ");    // extra zero to clear
    Serial.println("Photo transfer complete");
    myFile.close();    // close the SD card file
    
    // wait some time before taking next photo
    while((timeBetweenPhotos - elapsedTime) > 0){
      delay(10);
      // figure out how long inbetween photos
      timeBetweenPhotos = GetTimeBetweenPhotos();
      // get the updated time
      currentTime = millis();
      elapsedTime = (currentTime - startTime)/1000;
      lcd.setCursor(11, 1);
      lcd.print(timeBetweenPhotos - elapsedTime);
      lcd.print("   ");
    }
    
    // come out of power savings
    LCamera.StopPowerSavingsCmd();
    
    // clear anything left in the buffer
    delay(100);      
    while(Serial.available() > 0){
      Serial.read();
    }
    
  }
  Serial.end();
  
  // loop endlessly until Arduino powered off
  while(1){
  }
}

void OpenSDFile(int fileCounter){
  String fileBase = String("photo");
  char errorChar[50];
  int lengthErrorChar = 50;
  char fileNameChar[50]; 
  int lengthFileNameChar = 50;
  
  String fileName = String(fileBase);
  if(fileCounter < 10){
    fileName += ("0");
  }
  if(fileCounter < 100){
    fileName += ("0");
  }
  fileName += fileCounter;
  fileName += ".txt";
  fileName.toCharArray(fileNameChar, lengthFileNameChar);
  
  // open the file for write at end like the Native SD library  
  if (!myFile.open(fileNameChar, O_RDWR | O_CREAT | O_AT_END)) {
    // To display the error, first create a string
    String errorMessage = String();
    // append the error message to the string
    errorMessage += "Error opening photo file "; 
    errorMessage += fileCounter; 
    errorMessage += " for write failed";
    // conver the string to a char array and send to the error function
    errorMessage.toCharArray(errorChar, lengthErrorChar);
    sd.errorHalt(errorChar);
  }
  // feedback to the user
  lcd.clear();
  lcd.setCursor(0,1);    // line 2
  lcd.print("Photo ");
  lcd.print(fileCounter);
}

// return the size of a captured photo
word CapturePhoto(){
  
  // Take the actual photo
  LCamera.SendTakePhotoCmd(); 
  delay(250);
  LCamera.CheckPhotoCaptured();
  
  return(LCamera.GetFileSize());
}


void WriteBytesToFile(byte *serData, int numBytes){
  int j;
  
  // Print the image to a file
  for(j=0;j<numBytes;j++)
  {   
    if(serData[j]<0x10)
    {
      myFile.print(0, HEX);
    }
      myFile.print(serData[j], HEX);
      myFile.write(32);
  }  
  myFile.write(10);
}

unsigned int GetTimeBetweenPhotos()
{
  // read the potentiometer
  potentiometerValue = analogRead(POTENTIOMETER);
  timeBetweenPhotos = map(potentiometerValue, 0, 1023, 45, 90);
  
  return(timeBetweenPhotos);
}
